// Object是通过getter / setter来进行变化侦测，但是数组的push方法并不会触发getter / setter
// 我们可以使用一个拦截器覆盖Array.prototype。之后每当使用Array原型上的方法操作数组时，其实是使用的拦截器中的方法。

import Dep from "../1_Object的变化监测";

// 拦截器
//拦截器其实就是一个和Array.prototype一样的对象，里面包含的属性也一样，只不过这个对象中可以改变数组本身的方法被我们覆盖  push pop shift unshift reverse splice sort

// 下面进行代码实现
const arrayProto = Array.prototype

export const arrayMethods = Object.create(arrayProto)   //这句代码的 arrayMethods.__proto__ === arrayProto

['push', 'pop', 'shift', 'unshift', 'reverse', 'splice', 'sort'].forEach(method => {
  // 缓存原始方法
  const origin = arrayProto[method];
  // 对可以改变数组的方法进行封装
  // 当使用push方法的时候其实调用的是arrayMethods.push而arrayMethods.push是函数mutator
  // 在mutator函数执行原始的数组方法，我们可以在这个函数中进行一些其他的事情
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...args) {
      return origin.apply(this, args);
    },
    configurable: true,
    enumerable: true,
    writable: true
  })
})


// 使用拦截器覆盖数组原型
class Observer {
  constructor(value) {
    this.value = value
    if (Array.isArray(this.value)) {
      value.__proto__ = arrayMethods
    } else {
      this.walk(value)
    }
  }

  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }
}


// 将数组的方法挂载到数组的属性上
// 判断__proto__时刻可用
const hasProto = '__proto__' in {};
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)  //返回数组

// 改造Observer
class Observer {
  constructor(value) {
    this.value = value
    if (Array.isArray(this.value)) {
      // 修改
      // value.__proto__ = arrayMethods
      const argument = hasProto ? protoAugment : copyAugment
      argument(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }

  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }
}

function protoAugment(target, src, keys) {
  target.__proto__ = src
}

function copyAugment(target, src, keys) {
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}


// 如何收集依赖
function defineReactiveDep(data, key, val) {
  // let Dep = []
  // 增加递归子属性
  if (typeof val === 'object') {
    new Observer(val)
  }
  let Dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      //  Dep.push(window.target)
      Dep.depend(window.target)   //进行依赖收集
      // 收集数组依赖
      return val
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      // for (let i = 0; i < Dep.length; i++) {
      //   Dep[i](newVal, val)   //触发依赖
      // }
      Dep.notify()
      val = newVal
    }
  })
}

// Array在getter进行依赖收集，在拦截器中触发依赖。




// 依赖列表存在哪里

export default class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    remove(this.subs, sub);
  }
  depend() {
    if (window.target) {
      this.addSub(window.target);
    }
  }
  notify() {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}
class Observer {
  constructor(value) {
    this.value = value
    // 新增dep
    this.dep = new Dep()
    if (Array.isArray(this.value)) {
      // 修改
      // value.__proto__ = arrayMethods
      const argument = hasProto ? protoAugment : copyAugment
      argument(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }

  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }
}

// 依赖收集

function defineReactiveDep(data, key, val) {
  // let Dep = []
  // 增加递归子属性
  // if (typeof val === 'object') {
  //   new Observer(val)
  // }  修改
  let childOb = new observe(val)
  let Dep = new Dep();
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    writable: true,
    get: function () {
      //  Dep.push(window.target)
      Dep.depend(window.target)   //进行依赖收集
      // 收集数组依赖
      // 新增
      if (childOb) {
        childOb.dep.depend()
      }
      return val
    },
    set: function (newVal) {
      if (newVal == val) {
        return
      }
      // for (let i = 0; i < Dep.length; i++) {
      //   Dep[i](newVal, val)   //触发依赖
      // }
      Dep.notify()
      val = newVal
    }
  })
}

/**
 * 尝试为value创建一个observer实例
 * 如果创建成功直接返回一个新创建额Observer实例。
 * 如果value已经存在一个Observe实例，直接返回。
 */


function observe(value, asRootData) {
  if (!isObject(value)) {
    return
  }
  let ob;
  if (hasOwn(value, '__proto__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob
}

// 在拦截器中获取Observer实例
function def(obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    value: value,
    configurable: true,
    writable: true,
    enumerable: !!enumerable
  })
}

class Observer {
  constructor(value) {
    this.value = value
    // 新增dep
    this.dep = new Dep()
    def(value, '__ob__', this)   //新增  增加了一个不可枚举的值__ob__  并且这个属性的值指向的就是Observer的实例
    if (Array.isArray(this.value)) {
      // 修改
      // value.__proto__ = arrayMethods
      const argument = hasProto ? protoAugment : copyAugment
      argument(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }

  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }
}


['push', 'pop', 'shift', 'unshift', 'reverse', 'splice', 'sort'].forEach(method => {
  // 缓存原始方法
  const origin = arrayProto[method];
  // 对可以改变数组的方法进行封装
  // 当使用push方法的时候其实调用的是arrayMethods.push而arrayMethods.push是函数mutator
  // 在mutator函数执行原始的数组方法，我们可以在这个函数中进行一些其他的事情
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...args) {
      const ob = this.__ob__  // 新增
      const result = origin.apply(this, args);
      ob.dep.notify()  //发送通知
      return result
    },
    configurable: true,
    enumerable: true,
    writable: true
  })
})

// 现在被监测了的数据身上都会有一个__ob__属性来表示他们是响应式的


// 监测数组中数据的变化
class Observer {
  constructor(value) {
    this.value = value
    // 新增dep
    this.dep = new Dep()
    def(value, '__ob__', this)   //新增  增加了一个不可枚举的值__ob__  并且这个属性的值指向的就是Observer的实例
    if (Array.isArray(this.value)) {
      // 新增
      this.observeArray(value)
      // 修改
      // value.__proto__ = arrayMethods
      const argument = hasProto ? protoAugment : copyAugment
      argument(value, arrayMethods, arrayKeys)
    } else {
      this.walk(value)
    }
  }

  /**
   * walk会将每个属性都转换成getter/setter的形式来侦测变化
   * 这个方法只对Object时会被调用
   */
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactiveDep(obj, obj[i], obj[keys[i]])
    }
  }

  /**
   * 
   * @param {数组中元素} items 监测数组中的每一项
   */
  observeArray(items) {
    for (let i = 0; i < items.length; i++) {
      observe(items[i])
    }
  }
}


// 获取新增元素
['push', 'pop', 'shift', 'unshift', 'reverse', 'splice', 'sort'].forEach(method => {
  // 缓存原始方法
  const origin = arrayProto[method];
  // 对可以改变数组的方法进行封装
  // 当使用push方法的时候其实调用的是arrayMethods.push而arrayMethods.push是函数mutator
  // 在mutator函数执行原始的数组方法，我们可以在这个函数中进行一些其他的事情
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...args) {
      const ob = this.__ob__  // 新增
      let inserted
      switch (method) {
        case 'push':
        case 'unshift':
          inserted = args
          break;
        case 'splice':
          inserted = args.slice(2)
          break;
      }
      const result = origin.apply(this, args);
      if (inserted) ob.observeArray(inserted)//新增
      ob.dep.notify()  //发送通知
      return result
    },
    configurable: true,
    enumerable: true,
    writable: true
  })
})